package models;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import models.PokerTP.Blad;
import models.PokerTP.Card;
import models.PokerTP.Deck;
import models.PokerTP.Licytacja;
import models.PokerTP.PartPhase;
import models.PokerTP.Player;
import models.PokerTP.Table;

public class PlayTable extends Table {
	private long tableId;
	private long currentPlayerId = 0;
	private PlayPlayer[] players;
	private int bots;
	private int humans;
	private String tableName;
	private int entranceFee;
	private int tokensCount;
	private long makerId = 0;
	private int playerNum = -1;
	private boolean started = false;
	private int playersWithDoneAction = 0;
	private PartPhase phase = PartPhase.ENTRANCE_FEE;
	private Licytacja licytacja = null;
	public int answerTime = 120; // 2 min
	private StringBuilder messages;
	private int messagesCount;
	private int tableState = 0;
	private String ratingList = null;
	
	public PlayTable(String tableName, long tableId, long makerId, int humanPlayers, int computerPlayers, int startTokens, int entranceFee) throws Blad {
		this.tableId = tableId;
		this.makerId = makerId;
		this.bots = computerPlayers;
		this.humans = humanPlayers;
		this.tableName = tableName;
		this.tokensCount = startTokens;
		this.entranceFee = entranceFee;
		if(bots+humans > 4 || humans < 1) 
			throw new Blad("Maksymalna liczba graczy to 4, oraz co najmniej jeden z nich musi byc czlowiekiem.");
		players = new PlayPlayer[bots+humans];
		gracze = new Player[bots+humans];
		for(int i = humans; i < bots+humans; ++i) {
			PlayBot bot = new PlayBot();
			players[i] = bot;
			gracze[i] = bot;
		}
		messages = new StringBuilder();
		messagesCount = 0;
		talia = new Deck();
	}
	
	public boolean canStart() {
		int count = players.length;
		for(int i = 0; i < count; ++i)
			if(players[i] == null || !players[i].readyToPlay)
				return false;
		return true;
	}
	
	public PlayPlayer start() {
		if(!canStart() || started) return null;

		started = true;
		if(playerNum < 0)
			playerNum = 0;
		currentPlayerId = players[playerNum].playerId;
		phase = PartPhase.DEAL;
		messages = new StringBuilder();
		messagesCount = 0;
		tableState = 0;
		return players[playerNum];
	}
	
	
	public PlayPlayer next() {
		if(playersWithDoneAction == players.length) {
			return null;
		}
		
		playerNum = ++playerNum % players.length;
		currentPlayerId = players[playerNum].playerId;
		return players[playerNum];
	}
	
	public boolean nextPhase() {
		phase = phase.next();
		if(phase == PartPhase.FIRST_BIDDING/* || phase == PartPhase.SECOND_BIDDING*/) {
			licytacja = new Licytacja(players, players.length);
		}
		else {
			phase = null;
		}
		playersWithDoneAction = 0; // nikt nie wykonał jeszcze akcji w tej fazie
		return true;
	}
	
	public void jumpToNextPhase() { // unsafe
		
	}
	
	public void removeFromTable(long userId, int userNum) {
		if(userId != players[userNum].playerId) return;
		PlayPlayer[] plinfo = new PlayPlayer[players.length - 1];
		//Player[] playe = new Player[gracze.length - 1];
		for(int i = 0, j = 0; i < players.length; ++i, ++j) {
			if(i == userNum) { --j; continue; }
			plinfo[j] = players[i];
			//playe[j] = gracze[i]; // TODO: DEPRECATED
		}
		if(playerNum == userNum) {
			playerNum = (playerNum == 0) ? (gracze.length - 1) % plinfo.length :
				--playerNum % plinfo.length;
			licytacja.kolej = playerNum;
		}
		players = plinfo;
		//gracze = playe;
		if(phase == PartPhase.FIRST_BIDDING || phase == PartPhase.SECOND_BIDDING) {
			licytacja.gracze = plinfo;
			licytacja.g = plinfo.length;
		}
	}
	
	public long getCurrentPlayerId() {
		return currentPlayerId;
	}
	
	public String getAvaibleActionsForCurrentPlayer() {
		switch(phase)
		{
		case ENTRANCE_FEE:
			return "1";
		case DEAL:
			return "2";
		case FIRST_BIDDING:
			return getPossibleBiddingActionsForCurrentPlayer();
		case CARD_EXCHANGE:
			return "3";
		case SECOND_BIDDING:
			return getPossibleBiddingActionsForCurrentPlayer();
		case SHOW_CARDS:
			return "4";
		case SHOW_WINNERS:
			return "5";
		default:
			return null;
		}
	}
	
	public boolean[] getActionTableForCurrentUser() {
		boolean[] akcje = new boolean[12];
		for(int i = 0; i < 12; ++i)
			akcje[i] = false;
		switch(phase)
		{
		case ENTRANCE_FEE:
			akcje[1] = true;
		case DEAL:
			akcje[2] = true;
		case FIRST_BIDDING:
			setBiddingOptions(akcje);
		case CARD_EXCHANGE:
			akcje[3] = true;
		case SECOND_BIDDING:
			setBiddingOptions(akcje);
		case SHOW_CARDS:
			akcje[4] = true;
		case SHOW_WINNERS:
			akcje[5] = true;
		default:
			
		}
		return akcje;
	}
	
	public void setBiddingOptions(boolean[] akcje) {
		if((phase != PartPhase.FIRST_BIDDING && phase != PartPhase.SECOND_BIDDING) || licytacja == null)
			return;
		
		PlayPlayer pl = players[playerNum];
		
		//builder.append("6"); // fold
		akcje[6] = true;
		if(licytacja.canBet(pl, playerNum))
			akcje[7] = true;
		
		if(licytacja.canCall(pl, playerNum))
			akcje[8] = true;
		
		if(licytacja.canCheck(pl, playerNum))
			akcje[9] = true;
		
		if(licytacja.canRaise(pl, playerNum))
			akcje[10] = true;
		
		//builder.append(";11"); // all-in
		akcje[11] = true;

	}
	
	public String getPossibleBiddingActionsForCurrentPlayer() {
		if((phase != PartPhase.FIRST_BIDDING && phase != PartPhase.SECOND_BIDDING) || licytacja == null)
			return null;
		
		PlayPlayer pl = players[playerNum];
		StringBuilder builder = new StringBuilder();
		
		builder.append("6"); // fold
		if(licytacja.canBet(pl, playerNum))
			builder.append(";7");
		
		if(licytacja.canCall(pl, playerNum))
			builder.append(";8");
		
		if(licytacja.canCheck(pl, playerNum))
			builder.append(";9");
		
		if(licytacja.canRaise(pl, playerNum))
			builder.append(";10");
		
		builder.append(";11"); // all-in
		
		return builder.toString();
	}
	
	public Map<String, Boolean> getActionsForPlayer(long id) {
		PlayPlayer pl = getPlayerByServerId(id);
		if(pl == null) return null;
		Map<String, Boolean> akcje = new HashMap<String,Boolean>();
		akcje.put("start", false);
		akcje.put("takecards", false);
		akcje.put("changecards", false);
		akcje.put("showwinners", false);
		akcje.put("fold", false);
		akcje.put("bet", false);
		akcje.put("call", false);
		akcje.put("check", false);
		akcje.put("raise", false);
		akcje.put("allin", false);
		akcje.put("resign", true); // zawsze można zrezygnowac
		
		if(id != currentPlayerId && phase != null) {
			akcje.put("wait", true);
			return akcje;
		}
		
		switch(phase)
		{
		case ENTRANCE_FEE:
			if(pl.cash > entranceFee) {
				akcje.put("start", true);
				addMessage("Gracz " + pl.getNazwa() + " jest gotowy do gry.");
			}
			else
				addMessage("Gracz " + pl.getNazwa() + " nie moze wplacic wpisowego.");
			break;
		case DEAL:
			akcje.put("takecards", true);
			break;
		case FIRST_BIDDING:
			break;
		case CARD_EXCHANGE:
			akcje.put("changecards", true);
			break;
		case SECOND_BIDDING:
			break;
		case SHOW_CARDS:
			// kod 4, DEPRECATED
		case SHOW_WINNERS:
			akcje.put("showwinners", true);
			break;
		default:
			break;
		}
		
		if((phase == PartPhase.FIRST_BIDDING || phase == PartPhase.SECOND_BIDDING) && licytacja != null) {
			akcje.put("fold", true);
			if(licytacja.canBet(pl, playerNum))
				akcje.put("bet", true);
			
			if(licytacja.canCall(pl, playerNum))
				akcje.put("call", true);
			
			if(licytacja.canCheck(pl, playerNum))
				akcje.put("check", true);
			
			if(licytacja.canRaise(pl, playerNum))
				akcje.put("raise", true);
			
			akcje.put("allin", true);
		}
		
		return akcje;
	}
	
	public boolean performActionForPlayer(long id, String action, Object[] params) {
		PlayPlayer pl = getPlayerByServerId(id);
		
		if(action == null || pl == null) return false;
		
		if(action.compareTo("start") == 0) {
			pl.cash -= entranceFee;
			addMessage("Gracz " + pl.getNazwa() + " wplacil wpisowe.");
			++playersWithDoneAction;
		}
		else if(action.compareTo("takecards") == 0) {
			Card[] crds = new Card[5];
			for(int i = 0; i < 5; ++i)
				crds[i] = talia.wypiszzTali();
			pl.ustawReke(crds);
			++playersWithDoneAction;
		}
		else if(action.compareTo("changecards") == 0) {
			if(params == null || params.length == 0) return false;
			for(Object obj : params) {
				pl.zmien((int)obj);
			}
			++playersWithDoneAction;
		}
		else if(action.compareTo("showwinners") == 0) { // ranking (ang. rating)
			StringBuilder rating = new StringBuilder(players.length + 1);
			for(PlayPlayer p : players) {
				rating.append(String.format("%s,%d,%d;", p.getNazwa(), p.winsCount, p.cash));
			}
			ratingList = rating.toString();
			++playersWithDoneAction;
		}
		else if(action.compareTo("bet") == 0) {
			if(params == null || params.length == 0) return false;
			licytacja.bet(pl, playerNum, (int)params[0]);
			++playersWithDoneAction;
		}
		else if(action.compareTo("fold") == 0) {
			licytacja.fold(pl, playerNum);
			++playersWithDoneAction;
		}
		else if(action.compareTo("call") == 0) {
			licytacja.call(pl, playerNum);
			++playersWithDoneAction;
		}
		else if(action.compareTo("check") == 0) {
			licytacja.check(pl, playerNum);
			++playersWithDoneAction;
		}
		else if(action.compareTo("raise") == 0) {
			if(params == null || params.length == 0) return false;
			licytacja.raise(pl, playerNum, (int)params[0]);
			++playersWithDoneAction;
		}
		else if(action.compareTo("allin") == 0) {
			licytacja.all(pl, playerNum);
			++playersWithDoneAction;
		}
		else {
			return false;
		}
		
		return true;
	}
	
	private boolean removePoorPlayer(int neededCash) {
		for(int i = 0; i < players.length; ++i) {
			if(players[i].cash < neededCash) {
				removeFromTable(players[i].playerId, i);
				return true;
			}
		}
		return false;
	}
	
	private void removeThisTable() {
		models.Tables.removeTable(tableId);
	}
	
	private Card[] dealCards(Deck deck) {
		int playerCount = players.length; 
		Card[] reka = new Card[playerCount];
		for(int i = 0; i < playerCount; ++i)
			reka[i] = deck.wypiszzTali();
		return reka;
	}
	
	public synchronized void performAction(int actionId, long userId, Object[] addParameter) {
		if(userId != currentPlayerId && actionId != 1 && actionId != 0) return;
		if(actionId >= 6 && actionId <= 11 && licytacja == null) return;
		switch(actionId) 
		{
		case 0:
			for(PlayPlayer pi : players) {
				if(pi.playerId == userId)
					pi.readyToPlay = true;
			}
		case 1:
			if(userId == 0 && addParameter != null) {
				int wpisowe = (int)addParameter[0];
				while(removePoorPlayer(wpisowe));
				if(players.length < 2) removeThisTable();
				for(Player p : gracze)
					p.cash -= wpisowe;
				playerNum = new Random().nextInt(players.length); // pierwszy gracz
			}
			break; // wpłata wpisowego
		case 2:
			Deck d = null;
			if(talia == null) {
				d = new Deck();
				talia = d;
			}
			else
			{
				d = talia;
			}
			gracze[playerNum].ustawReke(dealCards(d));
			break; // gracz pobiera karty
		case 3:
			Player p = gracze[playerNum];
			for(int i = 0; i < addParameter.length; ++i) {
				p.zmien((int)addParameter[i]);
			}
			break; // wymiana kart przez gracza
		case 4:
			break; // pokaz wymienione karty
		case 5:
			break; // pokaz zwyciezcow
		case 6:
			licytacja.fold(gracze[playerNum], playerNum);
			break; // fold
		case 7:
			licytacja.bet(gracze[playerNum], playerNum, (int)addParameter[0]);
			break; // bet
		case 8:
			licytacja.call(gracze[playerNum], playerNum);
			break; // call
		case 9:
			licytacja.check(gracze[playerNum], playerNum);
			break; // check
		case 10:
			licytacja.raise(gracze[playerNum], playerNum, (int)addParameter[0]);
			break; // raise
		case 11:
			licytacja.all(gracze[playerNum], playerNum);
			break; // all-in	
		default:
			return;
		}
		++playersWithDoneAction;
	}
	
	public int getMissingPlayers() {
		int missingPlayers = humans;
		for(int i = 0; i < players.length; ++i) // 0. miejsce dla twórcy stołu
			if(players[i] != null && !players[i].isComputer)
				--missingPlayers;
		return missingPlayers; // jedno miejsce zarezerwowane dla twórcy
	}
	/*
	public boolean addHumanPlayer(long id, Human h) {
		return addHumanPlayer(id, new PlayerInfo(id, h));
	}*/
	
	public boolean addHumanPlayer(long id, PlayPlayer player) {
		if(getMissingPlayers() <= 0) return false;
		/*if(id == makerId) {
			players[0] = player;
			return true;
		}*/
		for(int i = 0; i < players.length; ++i) { // miejsce 0. zarezerwowane dla twórcy
			if(players[i] == null) {
				players[i] = player;
				//gracze[i] = player.getPlayer();
				return true;
			}
		}
		return false;
	}

	public String getTableName() {
		return tableName;
	}

	public void setTableName(String tableName) {
		this.tableName = tableName;
	}

	public int getEntranceFee() {
		return entranceFee;
	}

	public void setEntranceFee(int entranceFee) {
		this.entranceFee = entranceFee;
	}

	public int getTokensCount() {
		return tokensCount;
	}

	public void setTokensCount(int tokensCount) {
		this.tokensCount = tokensCount;
	}

	public boolean isStarted() {
		return started;
	}

	public int getBots() {
		return bots;
	}

	public long getTableId() {
		return tableId;
	}

	public void setTableId(long tableId) {
		this.tableId = tableId;
	}

	public long getMakerId() {
		return makerId;
	}

	public void setMakerId(long makerId) {
		this.makerId = makerId;
	}
	
	public boolean isMissingPlayer() {
		for(int i = 0; i < gracze.length; ++i)
			if(gracze[i] == null)
				return true;
		return false;
	}
	
	public String getMessages() {
		if(messages == null) return null;
		return messages.toString();
	}
	
	public void addMessage(String message) {
		if(messages == null) return;
		messages.insert(0, "<li>" + message + "</li>");
		++messagesCount;
		++tableState;
	}
	
	public int getMessagesCount() {
		return messagesCount;
	}
	
	public PlayPlayer getPlayerByServerId(long id) {
		for(PlayPlayer pl : players) {
			if(pl.playerId == id)
				return pl;
		}
		return null;
	}
	
	public int getState() { return tableState; }
	
	public String getRatingList() { return ratingList; }
}
